package main

import (
	"fmt"
	"net"
	"strconv"
	"strings"
	"sync"
	"time"
)

const Protocol = "tcp" // 通讯协议
const Delay = 200      // 延迟
const nodeNumber = 300 // 节点数
const maxConn = 15     // 节点最多连接节点数
const nodeStartNum = "8000"

var NodeAddrs []string = make([]string, nodeNumber) // 存放系统中的节点 ip:port

var IsAcceptMsg = make(map[string][]string) // 存放系统中节点接受数据的缓存
var IsAcceptMsgLock sync.Mutex

var NodeMapping = make(map[string][]string) // 存放节点之间映射关系
var NodeMappingLock sync.Mutex

func main() {
	// 组织系统中存在的节点地址
	startNodeID, _ := strconv.Atoi(nodeStartNum)

	for i := 0; i < nodeNumber; i++ {
		nodeAddr := "localhost:" + strconv.Itoa(startNodeID+i)
		NodeAddrs[i] = nodeAddr
	}

	// TODO 网络结构优化
	for index, nodeAddr := range NodeAddrs {
		if index == 0 {
			continue
		}

		if index%maxConn == 0 {
			parentNode := NodeAddrs[index-1-(index-1)%maxConn] // 拿到上一轮的parentNode数目
			NodeMapping[parentNode] = append(NodeMapping[parentNode], nodeAddr)

			NodeMapping[nodeAddr] = append(NodeMapping[nodeAddr], parentNode) // 完善代码
			continue
		}

		// 求该节点的链接节点
		parentNode := NodeAddrs[index-index%maxConn]
		NodeMapping[parentNode] = append(NodeMapping[parentNode], nodeAddr)
	}

	for _, nodeAddr := range NodeAddrs {
		// 如果该节点没有分配网络映射
		if _, ok := NodeMapping[nodeAddr]; !ok {
			// 拿到该节点在网络映射中对应的键值
			var key string // 节点的键值
			for i, item := range NodeMapping {
				for _, j := range item {
					if j == nodeAddr {
						key = i
						break
					}
				}
			}
			NodeMapping[nodeAddr] = []string{key}
		}
	}

	//fmt.Println(NodeMapping)

	StartServer()

	// 阻塞程序
	var ch chan struct{}
	<-ch
}

func StartServer() {
	for _, item := range NodeAddrs {
		go server(item)
	}
}

func server(nodeAddr string) {
	// 创建服务端
	listener, err := net.Listen(Protocol, nodeAddr)

	fmt.Printf("端口节点 < %s > 已启动...\n", nodeAddr)

	if err != nil {
		fmt.Println("服务器 listen err: ", err)
	}

	defer listener.Close()

	for {
		// 等待客户端连接请求
		conn, err := listener.Accept()

		if err != nil {
			fmt.Println("accept err:", err)
			return
		}
		// 处理用户请求，新建一个协程
		go HandleMulServerConn(conn, nodeAddr)
	}
}

// 处理发来的请求，实现多个服务端之间通讯
func HandleMulServerConn(conn net.Conn, nodeAddr string) {
	// 函数调用完毕，自动关闭conn
	defer conn.Close()

	buf := make([]byte, 2048)

	n, err := conn.Read(buf) // 读取用户数据

	if err != nil {
		fmt.Println("read err:", err)
		return
	}

	// 判断收到的数据是否在自己的缓存中，如果在，直接退出; 如果不在，广播给其他节点
	IsAcceptMsgLock.Lock() // // TODO map线程不安全
	isAcceptMsg := IsAcceptMsg[nodeAddr]
	IsAcceptMsgLock.Unlock()
	for _, item := range isAcceptMsg {
		// 如果在，直接退出
		if item == string(buf[:n]) {
			return
		}
	}

	IsAcceptMsgLock.Lock() // 加锁
	// 设置缓存最大长度
	if len(IsAcceptMsg[nodeAddr]) > nodeNumber*2 {
		// 清空该节点缓存
		delete(IsAcceptMsg, nodeAddr)
	}

	IsAcceptMsg[nodeAddr] = append(IsAcceptMsg[nodeAddr], string(buf[:n]))
	IsAcceptMsgLock.Unlock() // 解锁

	// 切分数据 data: hello_123456789
	data := strings.Split(string(buf[:n]), "_")

	fmt.Println("<" + nodeAddr + "> 收到数据:" + data[0])

	// 模拟延迟
	time.Sleep(Delay * time.Millisecond) // 设置延迟

	// 将收到的数据广播给连接的8个节点(循环发送)
	NodeMappingLock.Lock() // // TODO map线程不安全
	nodeMapping := NodeMapping[nodeAddr]
	NodeMappingLock.Unlock()
	for _, item := range nodeMapping {

		go sendMessage(item, buf[:n])
	}
}

// 以并发的方式建立链接发送数据
func sendMessage(item string, buf []byte) {
	// 建立客户端连接其他节点并发送，广播该该节点收到的数据
	clientConn, err := net.Dial(Protocol, item)
	defer clientConn.Close()

	if err != nil {
		fmt.Println("clientConn err:", item)
		return
	}
	//defer clientConn.Close()

	_, err = clientConn.Write(buf)

	if err != nil {
		fmt.Println("Write data err:", err)
		return
	}
}
